<script>
import { ref, onMounted, onUnmounted } from 'vue'
import { getPLCData, mapPLCDataToFrontend, getOperations, uploadExcel, getProjects, saveProjects, uploadTestResults } from './api.js'
import userStore from './store/userStore.js'

export default {
  name: 'App',
  setup() {
    // 信息数据
    const infoData = ref({
      station_name: '',
      project_name: '',
      product_number: '',
      count: '',
      trace_barcode: '',
      operator: '',
      rtp_version: '',
      software_version: '',
      product_status: '',
      qualified_count: '',
      unqualified_count: 0,
      concession_count: ''
    })

    // 测试项信息数据
    const testData = ref({
      Motor_id: '',
      EDCU_id: '',
      Wiring_barcode: '',
      Dielectric_test: false,
      Obstacle_test: false,
      Open_time: 0.0,
      Push_force: 0.0,
      Close_time: 0.0,
      Pull_force: 0.0
    })

    // 加载状态
    const loading = ref(false)
    const error = ref('')
    const lastUpdateTime = ref('')

    // 定时器
    let updateTimer = null

    // WebSocket相关变量
    let websocket = null
    let reconnectInterval = 1000 // 初始重连间隔（1秒）
    const maxReconnectInterval = 30000 // 最大重连间隔（30秒）
    const backend = 'localhost:8005' // 后端地址

    // 工序步骤数据
    const operations = ref([])
    const operationsLoading = ref(false)
    const operationsError = ref('')
    
    // 分页相关数据
    const currentPageIndex = ref(0)
    const paginatedOperations = ref([])
    
    // 跳转相关数据
    const jumpToStep = ref('')
    
    // 分页方法
    const groupOperationsByMainStep = (ops) => {
      const groups = []
      let currentMainStep = null
      let currentGroup = []
      
      ops.forEach(operation => {
        // 获取主工序编号（如 "1.1" -> "1", "2.3" -> "2"）
        const mainStepNumber = operation.step.split('.')[0]
        
        // 如果是新的主工序，开始新的分组
        if (currentMainStep !== mainStepNumber) {
          if (currentGroup.length > 0) {
            groups.push([...currentGroup])
          }
          currentMainStep = mainStepNumber
          currentGroup = [operation]
        } else {
          // 同一主工序的子步骤添加到当前分组
          currentGroup.push(operation)
        }
      })
      
      // 添加最后一个分组
      if (currentGroup.length > 0) {
        groups.push(currentGroup)
      }
      
      return groups
    }
    
    const updatePaginatedOperations = () => {
      paginatedOperations.value = groupOperationsByMainStep(operations.value)
      // 重置页面索引如果超出范围
      if (currentPageIndex.value >= paginatedOperations.value.length) {
        currentPageIndex.value = 0
      }
    }

    // 上传测试结果
    const handleUploadTestResults = async () => {
      // 检查是否所有测试都通过
      if (!isAllTestsPassed()) {
        testResultUploadMessage.value = '请确保所有测试项都通过后再上传'
        return
      }
      
      testResultUploadStatus.value = 'uploading'
      testResultUploadMessage.value = ''
      
      try {
        // 准备上传数据，包含所有测试项信息和追溯条码
        const uploadData = {
          // 测试项信息
          test_items: {
            Motor_id: testData.value.Motor_id,
            EDCU_id: testData.value.EDCU_id,
            Wiring_barcode: testData.value.Wiring_barcode,
            Dielectric_test: testData.value.Dielectric_test,
            Obstacle_test: testData.value.Obstacle_test,
            Open_time: testData.value.Open_time,
            Push_force: testData.value.Push_force,
            Close_time: testData.value.Close_time,
            Pull_force: testData.value.Pull_force
          },
          // 产品信息
          product_info: {
            trace_barcode: infoData.value.trace_barcode,
            project_name: infoData.value.project_name,
            product_number: infoData.value.product_number,
            operator: infoData.value.operator,
            rtp_version: infoData.value.rtp_version,
            software_version: infoData.value.software_version
          },
          // 时间戳
          timestamp: new Date().toISOString()
        }
        
        const response = await uploadTestResults(uploadData)
        
        if (response.status === 'success') {
          testResultUploadStatus.value = 'success'
          testResultUploadMessage.value = '上传成功'
          
          // 1秒后恢复原状
          setTimeout(() => {
            testResultUploadStatus.value = 'idle'
            testResultUploadMessage.value = ''
          }, 1000)
        } else {
          throw new Error(response.message || '上传失败')
        }
      } catch (error) {
        console.error('上传测试结果失败:', error)
        testResultUploadStatus.value = 'error'
        testResultUploadMessage.value = `上传失败: ${error.message}`
        
        // 1秒后恢复原状
        setTimeout(() => {
          testResultUploadStatus.value = 'idle'
          testResultUploadMessage.value = ''
        }, 1000)
      }
    }
    
    const goToPreviousPage = () => {
      if (currentPageIndex.value > 0) {
        currentPageIndex.value--
      }
    }
    
    const goToNextPage = () => {
      if (currentPageIndex.value < paginatedOperations.value.length - 1) {
        currentPageIndex.value++
      }
    }
    
    const getCurrentPageOperations = () => {
      if (paginatedOperations.value.length === 0) return []
      return paginatedOperations.value[currentPageIndex.value] || []
    }
    
    // 跳转到指定工序步骤
    const jumpToStepNumber = () => {
      const stepNumber = jumpToStep.value.trim()
      if (!stepNumber) {
        currentPageIndex.value = 0
        return
      }
      
      // 查找包含指定步骤编号的页面
      let targetPageIndex = -1
      
      for (let pageIndex = 0; pageIndex < paginatedOperations.value.length; pageIndex++) {
        const pageOperations = paginatedOperations.value[pageIndex]
        
        // 检查该页面是否包含目标步骤
        const hasTargetStep = pageOperations.some(operation => {
          // 检查主步骤
          if (operation.step === stepNumber) {
            return true
          }
          
          // 检查子步骤
          if (operation.sub_operations && operation.sub_operations.length > 0) {
            return operation.sub_operations.some(subOp => subOp.step === stepNumber)
          }
          
          return false
        })
        
        if (hasTargetStep) {
          targetPageIndex = pageIndex
          break
        }
      }
      
      // 如果找到目标页面，跳转到该页面；否则跳转到第一页
      currentPageIndex.value = targetPageIndex >= 0 ? targetPageIndex : 0
      
      // 清空输入框
      jumpToStep.value = ''
    }

    // 设置面板状态
    const showSettings = ref(false)
    const selectedFile = ref(null)
    const filePath = ref('')
    const uploadStatus = ref('')

    // 项目列表弹窗状态
    
    // 上传测试结果状态管理
    const testResultUploadStatus = ref('idle')
    const testResultUploadMessage = ref('')
    const wsErrorMessage = ref('')
    const wsErrorProjectCode = ref('')


    // 获取PLC数据
    const fetchPLCData = async () => {
      try {
        loading.value = true
        error.value = ''
        
        const response = await getPLCData()
        const mappedData = mapPLCDataToFrontend(response)
        
        // 更新产品信息数据
        infoData.value.station_name = mappedData.infoData.stationName
        infoData.value.project_name = userStore.get('project_name') || mappedData.infoData.project_name // 如果用户没有userStore.get('project_name')，才使用后端数据
        infoData.value.product_number = mappedData.infoData.productNumber
        infoData.value.count = userStore.get('count') || mappedData.infoData.count
        infoData.value.trace_barcode = userStore.get('trace_barcode') || mappedData.infoData.trace_barcode // 优先使用userStore中的追溯条码
        
        infoData.value.operator = userStore.get('operator') || mappedData.infoData.operator
        infoData.value.rtp_version = mappedData.infoData.rtpVersion
        infoData.value.software_version = userStore.get('software_version') || mappedData.infoData.software_version
        infoData.value.product_status = mappedData.infoData.productStatus
        infoData.value.qualified_count = mappedData.infoData.qualifiedCount
        infoData.value.unqualified_count = mappedData.infoData.unqualifiedCount
        infoData.value.concession_count = mappedData.infoData.concessionCount
        
        // 更新测试项数据
        testData.value.Motor_id = mappedData.testItems.motorId
        testData.value.EDCU_id = mappedData.testItems.controllerId
        testData.value.Dielectric_test = mappedData.testItems.closingForceTest
        testData.value.Obstacle_test = mappedData.testItems.antiPinchTest
        testData.value.Open_time = mappedData.testItems.openTime
        testData.value.Wiring_barcode = mappedData.testItems.harnessBarcode
        testData.value.Push_force = mappedData.testItems.pushForce
        testData.value.Close_time = mappedData.testItems.closeTime
        testData.value.Pull_force = mappedData.testItems.pullForce
        
        lastUpdateTime.value = new Date().toLocaleString()
      } catch (err) {
        error.value = `连接后端失败: ${err.message}`
        console.error('获取PLC数据失败:', err)
      } finally {
        loading.value = false
      }
    }

    // 启动定时更新
    const startAutoUpdate = () => {
      // 立即获取一次数据
      fetchPLCData()
      
      // 每5秒更新一次数据
      updateTimer = setInterval(() => {
        fetchPLCData()
      }, 1000)
    }

    // 停止定时更新
    const stopAutoUpdate = () => {
      if (updateTimer) {
        clearInterval(updateTimer)
        updateTimer = null
      }
    }

    // WebSocket连接管理
    const initWebSocket = () => {
      console.log('App页面开始初始化WebSocket连接...')
      
      // 关闭已存在的连接
      if (websocket) {
        console.log('关闭现有WebSocket连接')
        websocket.close()
      }
      
      // 创建新的WebSocket连接
      console.log(`创建新的WebSocket连接: ws://${backend}/ws/scanner`)
      websocket = new WebSocket(`ws://${backend}/ws/scanner`)
      
      // 设置事件处理器
      setupWebSocketHandlers()
    }

    // 设置WebSocket事件处理器
    const setupWebSocketHandlers = () => {
      // 连接建立时的回调
      websocket.onopen = () => {
        console.log('App页面WebSocket连接已成功建立')
        reconnectInterval = 1000 // 重置重连间隔
      }
      
      // 接收消息的回调
      websocket.onmessage = (event) => {
        try {
          console.log('App页面收到WebSocket消息:', event.data)
          const message = JSON.parse(event.data)
          
          // 处理field_update类型的消息
          if (message.type === 'field_update') {
            console.log(`App页面更新字段: ${message.field_name} = ${message.field_value}`)
            
            // 动态更新项目名称字段
            if (message.field_name === 'project_name') {
              infoData.value.project_name = message.field_value
              // 同时更新userStore中的项目名称，防止下次加载时被覆盖
              userStore.set('project_name', message.field_value)
              lastUpdateTime.value = new Date().toLocaleString()
              console.log('App页面项目名称已更新:', message.field_value)
            }
            
            // 动态更新追溯条码字段
            if (message.field_name === 'trace_barcode') {
              infoData.value.trace_barcode = message.field_value
              // 同时更新userStore中的追溯条码，防止下次加载时被覆盖
              userStore.set('trace_barcode', message.field_value)
              lastUpdateTime.value = new Date().toLocaleString()
              console.log('App页面追溯条码已更新:', message.field_value)
            }
          }
          if (message.type === 'project_not_configured') {
            wsErrorProjectCode.value = message.project_code
            wsErrorMessage.value = `项目编号未配置: ${message.project_code}`
          }
        } catch (err) {
          console.error('App页面WebSocket消息解析错误:', err)
        }
      }
      
      // 连接关闭时的回调
      websocket.onclose = () => {
        console.log('App页面WebSocket连接已关闭')
        // 自动重连
        setTimeout(() => {
          console.log(`App页面${reconnectInterval}ms后尝试重连WebSocket...`)
          initWebSocket()
          // 增加重连间隔，但不超过最大值
          reconnectInterval = Math.min(reconnectInterval * 2, maxReconnectInterval)
        }, reconnectInterval)
      }
      
      // 连接错误时的回调
      websocket.onerror = (error) => {
        console.error('App页面WebSocket连接错误:', error)
        // 主动关闭以触发 onclose 重连
        if (websocket) {
          websocket.close()
        }
      }
    }

    // 页面可见性变化处理
    const handleVisibilityChange = () => {
      if (document.visibilityState === 'visible') {
        // 页面变为可见时，如果WebSocket未连接则重新连接
        if (!websocket || websocket.readyState !== WebSocket.OPEN) {
          console.log('App页面可见，确保WebSocket连接')
          initWebSocket()
        }
      }
    }

    // 获取工序步骤数据
    const fetchOperations = async () => {
      try {
        operationsLoading.value = true
        operationsError.value = ''
        
        const response = await getOperations()
        operations.value = response || []
        
        // 数据更新后更新分页
        updatePaginatedOperations()
      } catch (err) {
        // 如果是未找到Excel文件的错误，不显示错误信息，而是显示上传提示
        if (err.message && err.message.includes('未找到Excel文件')) {
          operations.value = []
          operationsError.value = ''
        } else {
          operationsError.value = `获取工序步骤失败: ${err.message}`
        }
        console.error('获取工序步骤失败:', err)
      } finally {
        operationsLoading.value = false
      }
    }

    // 切换设置面板
    const toggleSettings = () => {
      showSettings.value = !showSettings.value
    }

    // 切换项目列表弹窗
    const toggleProjectList = () => { userStore.set('openProjectList', true) }

    // 切换测试项状态（操作暂存列表）
    

    // 判断所有测试是否通过
    const isAllTestsPassed = () => {
      // 布尔类型测试项：必须为 true
      const booleanTests = [
        testData.value.Dielectric_test,  // 耐压测试
        testData.value.Obstacle_test     // 防夹测试
      ]
      
      // 数值类型测试项：必须大于 0
      const numericTests = [
        testData.value.Push_force,       // 推力测试
        testData.value.Pull_force,       // 拉力测试
        testData.value.Open_time,        // 开门时间
        testData.value.Close_time        // 关门时间
      ]
      
      // 检查所有布尔测试项是否都为 true
      const allBooleanTestsPassed = booleanTests.every(test => test === true)
      
      // 检查所有数值测试项是否都大于 0
      const allNumericTestsPassed = numericTests.every(test => test > 0)
      
      return allBooleanTestsPassed && allNumericTestsPassed
    }

    // 处理文件选择
    const handleFileSelect = (event) => {
      const file = event.target.files[0]
      if (file) {
        selectedFile.value = file
        filePath.value = file.name
        uploadStatus.value = ''
      }
    }

    // 上传Excel文件
    const handleUploadExcel = async () => {
      if (!selectedFile.value) {
        uploadStatus.value = '请先选择Excel文件'
        return
      }
      
      uploadStatus.value = '正在上传...'
      
      try {
        const response = await uploadExcel(selectedFile.value)
        uploadStatus.value = '上传成功'
        
        // 上传成功后重新获取工序步骤数据
        await fetchOperations()
        
        // 隐藏设置面板
        showSettings.value = false
        
        // 清空文件选择
        selectedFile.value = null
        filePath.value = ''
      } catch (error) {
        console.error('上传Excel文件失败:', error)
        if (error.message.includes('Failed to fetch')) {
          uploadStatus.value = '后端服务未启动，请先启动后端服务'
        } else {
          uploadStatus.value = `上传失败: ${error.message}`
        }
      }
    }

    // 组件挂载时启动定时更新
    onMounted(() => {
      // 如果有登录用户，立即设置相关信息
      if (userStore.get('operator')) {
        infoData.value.operator = userStore.get('operator')
      }
      if (userStore.get('project_name')) {
        infoData.value.project_name = userStore.get('project_name')
      }
      if (userStore.get('count')) {
        infoData.value.count = userStore.get('count')
      }
      if (userStore.get('software_version')) {
        infoData.value.software_version = userStore.get('software_version')
      }
      if (userStore.get('trace_barcode')) {
        infoData.value.trace_barcode = userStore.get('trace_barcode')
      }
      
      startAutoUpdate()
      fetchOperations() // 初始化时获取工序步骤数据
      initWebSocket() // 初始化WebSocket连接
      
      // 监听页面可见性变化
      document.addEventListener('visibilitychange', handleVisibilityChange)

    })

    // 组件卸载时清理定时器
    onUnmounted(() => {
      stopAutoUpdate()
      
      // 关闭WebSocket连接
      if (websocket) {
        console.log('App页面组件卸载，关闭WebSocket连接')
        websocket.close()
        websocket = null
      }
      
      // 移除页面可见性监听器
      document.removeEventListener('visibilitychange', handleVisibilityChange)
    })

    const openConfigFromError = () => {
      if (wsErrorProjectCode.value) {
        userStore.set('prefill_project_code', wsErrorProjectCode.value)
      }
      userStore.set('openProjectList', true)
      wsErrorMessage.value = ''
      wsErrorProjectCode.value = ''
    }

    return {
      infoData,
      testData,
      loading,
      error,
      lastUpdateTime,
      operations,
      operationsLoading,
      operationsError,
      showSettings,
      selectedFile,
      filePath,
      uploadStatus,
      currentPageIndex,
      paginatedOperations,
      jumpToStep,
      wsErrorMessage,
      
      toggleSettings,
      toggleProjectList,
      handleFileSelect,
      handleUploadExcel,
      handleUploadTestResults,
      testResultUploadStatus,
      testResultUploadMessage,
      wsErrorProjectCode,
      openConfigFromError,
      goToPreviousPage,
      goToNextPage,
      getCurrentPageOperations,
      jumpToStepNumber,
      isAllTestsPassed
    }
  }
}
</script>

<template>
  <div class="app">
    <!-- 顶部导航栏 -->
    <header class="header">
      <div class="header-container">
        <div class="logo-section">
          <img src="../assets/icon/raw_logo.jpg" alt="Wabtec Logo" class="logo" />
        </div>
        <h2 class="system-title">
          产品生产记录系统
        </h2>
        <div class="status-panel">
          <div v-if="loading" class="loading-indicator">
            <div class="spinner"></div>
            正在更新数据...
          </div>
          <div v-else-if="lastUpdateTime" class="update-time">
            <span class="status-icon">✓</span>
            最后更新: {{ lastUpdateTime }}
          </div>
          <div v-if="error" class="error-indicator">
            <span class="status-icon">⚠️</span>
            {{ error }}
          </div>
        </div>
        <button @click="toggleProjectList" class="project-list-btn" type="button" style="margin-left: 12px;">项目配置</button>
      </div>
    </header>

    <!-- 设置面板 -->
    <div v-if="showSettings" class="settings-panel">
      <div class="settings-content">
        <h3>⚙️ 系统设置</h3>
        <div class="file-row">
          <div class="file-controls">
            <label for="excel-file" class="file-button">
              <span class="file-icon">📄</span>
              选择Excel文件
            </label>
            <div class="file-path">{{ filePath || '请选择工作计划文件 (.xlsx, .xls)' }}</div>
            <button @click="handleUploadExcel" class="upload-button" :disabled="!filePath">
              <span style="margin-right: 6px;">⬆️</span>
              上传文件
            </button>
          </div>
          <input type="file" id="excel-file" accept=".xlsx,.xls" @change="handleFileSelect" class="file-input">
        </div>
        <div v-if="uploadStatus" class="upload-status">
          <span style="margin-right: 8px;">ℹ️</span>
          {{ uploadStatus }}
        </div>
        <div class="button-group">
          <button @click="toggleSettings" class="close-button">
            <span style="margin-right: 6px;">✖️</span>
            关闭
          </button>
        </div>
      </div>
    </div>


    <!-- 主内容区域 -->
    <main class="main-content">

      <!-- 内容布局 -->
      <div class="content-layout">
        <!-- 左侧内容区域 (60%) -->
        <div class="left-section">
          <!-- 产品信息区域 -->
          <div class="info-data card">
            <div class="section-header">
              <h3 class="section-title">机构测试</h3>
            </div>
            <div class="info-grid">
              <!-- 第一行 -->
              <div class="info-item">
                <!-- <label>工位名称：</label>
                <input type="text" :value="infoData.station_name" readonly /> -->
              </div>
              <div class="info-item">
                <label>追溯条码：</label>
                <input type="text" :value="infoData.trace_barcode" readonly />
              </div>
              <div class="info-item">
                <label>测试结果：</label>
                <button 
                  class="upload-test-result-btn"
                  :class="{
                    'test-passed': isAllTestsPassed() && testResultUploadStatus === 'idle',
                    'test-failed': !isAllTestsPassed() && testResultUploadStatus === 'idle',
                    'uploading': testResultUploadStatus === 'uploading',
                    'success': testResultUploadStatus === 'success',
                    'error': testResultUploadStatus === 'error'
                  }"
                  :disabled="testResultUploadStatus === 'uploading'"
                  @click="handleUploadTestResults"
                  type="button"
                >
                  <span v-if="testResultUploadStatus === 'idle'">上传测试结果</span>
                  <span v-else-if="testResultUploadStatus === 'uploading'" class="uploading-content">
                    <span class="spinner-small"></span>
                    上传中
                  </span>
                  <span v-else-if="testResultUploadStatus === 'success'" class="success-content">
                    <span class="checkmark">✓</span>
                    上传成功
                  </span>
                  <span v-else-if="testResultUploadStatus === 'error'">
                    {{ testResultUploadMessage }}
                  </span>
                </button>
              </div>

              <!-- 第二行 -->
              <div class="info-item">
                <label>项目名称：</label>
                <input type="text" :value="infoData.project_name" readonly />
              </div>
              <div class="info-item">
                <label>操作人员：</label>
                <input type="text" :value="infoData.operator" readonly />
              </div>
              <div class="info-item">
                <label>合格品统计：</label>
                <input type="text" :value="infoData.qualified_count" readonly />
              </div>

              <!-- 第三行 -->
              <div class="info-item">
                <label>产品编号：</label>
                <input type="text" :value="infoData.product_number" readonly />
              </div>
              <div class="info-item">
                <label>产品版本：</label>
                <input type="text" :value="infoData.rtp_version" readonly />
              </div>
              <div class="info-item">
                <label>不良品统计：</label>
                <input type="number" :value="infoData.unqualified_count" readonly />
              </div>

              <!-- 第四行 -->
              <div class="info-item">
                <label>列数：</label>
                <input type="text" :value="infoData.count" readonly />
              </div>
              <div class="info-item">
                <label>软件版本：</label>
                <input type="text" :value="infoData.software_version" readonly />
              </div>
              <div class="info-item">
                <label>让步放行统计：</label>
                <input type="text" :value="infoData.concession_count" readonly />
              </div>
            </div>
          </div>

          <!-- 测试项信息区域 -->
          <div class="test-info card">
            <h3 class="section-title">测试项信息</h3>
            <div class="test-grid">
              <!-- 第一行 -->
              <div class="test-item">
                <label>电机序号：</label>
                <input type="text" :value="testData.Motor_id" readonly />
              </div>
              <div class="test-item">
                <label> </label>
              </div>
              <div class="test-item">
                <label> </label>
              </div>
              <div class="test-item">
                <label> </label>
              </div>

              <!-- 第二行 -->
              <div class="test-item">
                <label>门控器序号：</label>
                <input type="text" :value="testData.EDCU_id" readonly />
              </div>
              <div class="test-item">
                <label>耐压测试：</label>
                <input type="text" :value="testData.Dielectric_test ? 'OK' : ''" readonly />
              </div>
              <div class="test-item">
                <label>防夹测试：</label>
                <input type="text" :value="testData.Obstacle_test ? 'OK' : ''" readonly />
              </div>
              <div class="test-item">
                <label>开门时间：</label>
                <input type="text" :value="testData.Open_time ? testData.Open_time.toFixed(1) : '0.0'" readonly />
                <span class="unit">S</span>
              </div>

              <!-- 第三行 -->
              <div class="test-item">
                <label>线束条码：</label>
                <input type="text" :value="testData.Wiring_barcode" readonly />
              </div>
              <div class="test-item">
                <label>推力测试：</label>
                <input type="text" :value="testData.Push_force ? (testData.Push_force / 100).toFixed(2) : '0.00'" readonly />
                <span class="unit">N</span>
              </div>
              <div class="test-item">
                <label>关门时间：</label>
                <input type="text" :value="testData.Close_time ? testData.Close_time.toFixed(1) : '0.0'" readonly />
                <span class="unit">S</span>
              </div>
              <div class="test-item">
                <label>拉力测试：</label>
                <input type="text" :value="testData.Pull_force ? (testData.Pull_force / 100 ).toFixed(2): '0.00'" readonly />
                <span class="unit">N</span>
              </div>
            </div>
          </div>
        </div>

        <!-- 右侧内容区域 (40%) -->
        <div class="right-section">
          <div class="process-steps card">
            <div class="section-header">
              <h3 class="section-title">工序步骤</h3>
              <button @click="toggleSettings" class="project-list-btn" type="button">上传工序步骤文件</button>
            </div>
            <div v-if="operationsLoading" class="loading-indicator">
              <div class="spinner"></div>
              正在加载工序步骤...
            </div>
            <div v-else-if="operationsError" class="error-indicator">
              <span class="status-icon">⚠️</span>
              {{ operationsError }}
            </div>
            <div v-else-if="operations.length === 0" class="upload-prompt">
              <div class="upload-prompt-content">
                <div class="upload-icon">📄</div>
                <h4>请上传工序步骤Excel</h4>
                <p>暂无工序步骤数据，请上传Excel文件以显示工序步骤</p>
                <button @click="toggleSettings" class="upload-prompt-button">
                  <span class="button-icon">⬆️</span>
                  上传Excel文件
                </button>
              </div>
            </div>
            <div v-else class="operations-container">
              <!-- 分页显示区域 -->
              <div class="operations-page">
                <div v-for="(operation, index) in getCurrentPageOperations()" :key="index" class="operation-item">
                  <div class="operation-header">
                    <span class="operation-number">{{ operation.step }}</span>
                    <span class="operation-title">{{ operation.operation }}</span>
                  </div>
                  <div v-if="operation.sub_operations && operation.sub_operations.length > 0" class="sub-operations">
                    <div v-for="(subOp, subIndex) in operation.sub_operations" :key="subIndex" class="sub-operation">
                      <span class="sub-operation-number">{{ subOp.step }}</span>
                      <span class="sub-operation-title">{{ subOp.operation }}</span>
                    </div>
                  </div>
                </div>
              </div>
              
              <!-- 分页控制按钮 -->
              <div v-if="paginatedOperations.length > 1" class="pagination-controls">
                <button 
                  @click="goToPreviousPage" 
                  :disabled="currentPageIndex === 0"
                  class="pagination-btn prev-btn"
                >
                  <span class="btn-icon">◀</span>
                  上一步
                </button>
                
                <div class="page-indicator">
                  <span class="current-page">{{ currentPageIndex + 1 }}</span>
                  <span class="page-separator">/</span>
                  <span class="total-pages">{{ paginatedOperations.length }}</span>
                </div>
                
                <button 
                  @click="goToNextPage" 
                  :disabled="currentPageIndex === paginatedOperations.length - 1"
                  class="pagination-btn next-btn"
                >
                  下一步
                  <span class="btn-icon">▶</span>
                </button>
              </div>
              
              <!-- 跳转功能 -->
              <div v-if="paginatedOperations.length > 1" class="jump-controls">
                <div class="jump-input-group">
                  <label for="jump-step" class="jump-label">跳转到工序：</label>
                  <input 
                    id="jump-step"
                    v-model="jumpToStep" 
                    type="text" 
                    placeholder="输入工序编号 (如: 1, 2.1, 3.2)"
                    class="jump-input"
                    @keyup.enter="jumpToStepNumber"
                  />
                  <button 
                    @click="jumpToStepNumber" 
                    class="jump-btn"
                  >
                    跳转
                  </button>
                </div>
                <div class="jump-hint">
                  提示：输入工序编号如 1、2、1.1、2.1 等，输入不合法时将跳转到第一页
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </main>
    <div v-if="wsErrorMessage" class="notification-toast error">
      <div class="toast-content">
        <div class="toast-icon" style="background: linear-gradient(135deg, #dc3545, #b71c1c);">
          <span class="notification-icon">❌</span>
        </div>
        <div class="toast-message">
          <span class="toast-text">{{ wsErrorMessage }}</span>
        </div>
        <button class="add-project-btn" style="margin-left:8px;" @click="openConfigFromError">去配置</button>
        <div class="toast-close" @click="wsErrorMessage = ''">
          <span>×</span>
        </div>
      </div>
      <div class="toast-progress" style="background: linear-gradient(90deg, #dc3545, #b71c1c);"></div>
    </div>
  </div>
</template>

<style scoped>
.app {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #c9ccd6;
}

/* 顶部导航栏 */
.header {
  background: white;
  color: #333;
  padding: 0 30px 0 0;
  height: 70px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.header-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  height: 100%;
  padding: 0 20px;
}

.logo-section {
  display: flex;
  align-items: center;
  gap: 15px;
}

.logo {
  height: 71px;
  width: auto;
  object-fit: contain;
  margin: 10px 0;
}

.company-name {
  font-size: 24px;
  font-weight: 600;
  margin: 0;
}

.system-title {
  flex: 1;
  text-align: center;
  font-size: 28px;
  font-weight: 600;
  margin: 0;
  color: #333;
}

/* 主内容区域 */
.main-content {
  flex: 1;
  padding: 20px 30px;
  overflow-y: auto;
}



/* 内容布局 */
.content-layout {
  display: flex;
  gap: 20px;
  min-height: calc(100vh - 200px);
}

.left-section {
  flex: 0 0 60%;
  display: flex;
  flex-direction: column;
  gap: 0px;
}

.right-section {
  flex: 0 0 38%;
}

/* 产品信息区域 */
.info-data {
  flex-shrink: 0;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.section-title {
  font-size: 18px;
  color: var(--primary-color);
  margin: 0;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--primary-color);
  flex: 1;
}

.project-list-btn {
  background-color: var(--primary-color);
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: background-color 0.3s ease;
  margin-left: 20px;
}

.project-list-btn:hover {
  background-color: #1976d2;
}

.project-list-btn:active {
  background-color: #0d47a1;
}

.info-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
  align-items: center;
}

.test-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 15px;
  margin-top: 15px;
}

.test-item {
  display: flex;
  flex-direction: column;
  gap: 5px;
  position: relative;
}

.test-item .unit {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--dark-gray);
  font-size: 12px;
  font-weight: 500;
  pointer-events: none;
  margin-top: 12px;
}

.test-item label {
  font-weight: 600;
  color: var(--dark-gray);
  font-size: 14px;
}

.test-item input {
  width: 100%;
  padding: 10px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 14px;
  transition: all 0.3s ease;
}

.test-item input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1);
}

.test-item input[readonly] {
  background-color: #f8f9fa;
  border-color: #e9ecef;
  color: #495057;
  cursor: default;
}

.test-item input[readonly]:focus {
  border-color: #e9ecef;
  box-shadow: none;
}

.info-item {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.info-item label {
  font-weight: 600;
  color: var(--dark-gray);
  font-size: 14px;
}

.info-item input {
  width: 100%;
  padding: 10px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 14px;
  transition: all 0.3s ease;
}

.info-item input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(44, 90, 160, 0.1);
}

.info-item input[readonly] {
  background-color: #f8f9fa;
  border-color: #e9ecef;
  color: #495057;
  cursor: default;
}

.info-item input[readonly]:focus {
  border-color: #e9ecef;
  box-shadow: none;
}

/* 上传测试结果按钮样式 */
.upload-test-result-btn {
  width: 100%;
  padding: 10px 16px;
  border: none;
  border-radius: 6px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  text-transform: none;
  letter-spacing: 0.5px;
}

.upload-test-result-btn.test-failed {
  background-color: #6c757d;
  color: white;
  box-shadow: 0 2px 4px rgba(108, 117, 125, 0.2);
}

.upload-test-result-btn.test-failed:hover {
  background-color: #5a6268;
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
}

.upload-test-result-btn.test-passed {
  background-color: #28a745;
  color: white;
  box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
}

.upload-test-result-btn.test-passed:hover {
  background-color: #218838;
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(40, 167, 69, 0.3);
}

/* 上传中状态 */
.upload-test-result-btn.uploading {
  background-color: #6c757d;
  color: white;
  cursor: not-allowed;
  opacity: 0.8;
}

.upload-test-result-btn.uploading:disabled {
  pointer-events: none;
}

/* 上传成功状态 */
.upload-test-result-btn.success {
  background-color: #28a745;
  color: white;
  animation: successPulse 0.6s ease-in-out;
}

/* 上传失败状态 */
.upload-test-result-btn.error {
  background-color: #dc3545;
  color: white;
  font-size: 12px;
}

/* 小型旋转动画 */
.spinner-small {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid transparent;
  border-top: 2px solid currentColor;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-right: 6px;
}

/* 成功图标样式 */
.checkmark {
  display: inline-block;
  margin-right: 6px;
  font-weight: bold;
  animation: checkmarkBounce 0.6s ease-in-out;
}

/* 上传内容容器 */
.uploading-content,
.success-content {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 成功脉冲动画 */
@keyframes successPulse {
  0% {
    transform: scale(1);
    box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
  }
  50% {
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(40, 167, 69, 0.4);
  }
  100% {
    transform: scale(1);
    box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
  }
}

/* 打勾动画 */
@keyframes checkmarkBounce {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}

/* 旋转动画 */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* 测试项信息区域 */
.test-info {
  flex: 1;
  min-height: 200px;
}

.placeholder-content {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 150px;
  background-color: var(--light-gray);
  border-radius: 4px;
  color: var(--medium-gray);
  font-style: italic;
}

/* 响应式设计 */
@media (max-width: 900px) {
  .content-layout {
    flex-direction: column;
  }
  
  .left-section {
    flex: none;
  }
  
  .right-section {
    flex: none;
  }
  
  .info-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 768px) {
  .header {
    padding: 10px 15px;
  }
  
  .company-name {
    font-size: 18px;
  }
  
  .main-content {
    padding: 15px;
  }
  
  .info-grid {
    grid-template-columns: 1fr;
  }
  
  .system-title {
    font-size: 22px;
  }
}

/* 状态面板样式 */
.status-panel {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  min-width: 200px;
  font-size: 12px;
}

/* 加载和状态指示器样式 */
.loading-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #2196F3;
  font-weight: 500;
}

.loading-indicator .spinner {
  width: 14px;
  height: 14px;
  border: 2px solid #f3f3f3;
  border-top: 2px solid #2196F3;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.update-time {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #28a745;
  font-weight: 500;
}

.error-indicator {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #dc3545;
  font-weight: 500;
  background-color: #fff5f5;
  padding: 4px 8px;
  border-radius: 4px;
  border-left: 3px solid #dc3545;
}

  .status-icon {
    font-size: 14px;
  }

  .notification-toast {
    position: fixed;
    top: 20px;
    right: 20px;
    min-width: 320px;
    max-width: 400px;
    background: #ffffff;
    border-radius: 12px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
    border: 1px solid #e2e8f0;
    overflow: hidden;
    z-index: 9999;
    animation: slideInRight 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  }
  .notification-toast.error .toast-text { color: #b71c1c; }
  .toast-content { display:flex; align-items:center; padding:16px 20px; gap:12px; }
  .toast-icon { flex-shrink:0; width:32px; height:32px; display:flex; align-items:center; justify-content:center; border-radius:50%; }
  .notification-icon { font-size:16px; color:white; }
  .toast-message { flex:1; min-width:0; }
  .toast-text { font-size:14px; font-weight:500; line-height:1.4; word-wrap:break-word; }
  .toast-close { flex-shrink:0; width:24px; height:24px; display:flex; align-items:center; justify-content:center; cursor:pointer; border-radius:50%; transition:background-color 0.2s ease; }
  .toast-close:hover { background-color:#f3f4f6; }
  .toast-close span { color:#9ca3af; font-size:18px; font-weight:500; line-height:1; }
  .toast-progress { height:3px; animation: progressBar 3s ease-in-out; }
  @keyframes slideInRight { from { opacity:0; transform: translateX(100%);} to { opacity:1; transform: translateX(0);} }
  @keyframes progressBar { from { width:0%; } to { width:100%; } }

  .add-project-btn {
    background-color: #0d47a1;
    color: #ffffff;
    border: none;
    padding: 8px 14px;
    border-radius: 6px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: background-color 0.2s ease, transform 0.1s ease;
    box-shadow: 0 2px 4px rgba(13, 71, 161, 0.2);
  }
  .add-project-btn:hover { background-color: #1976d2; }
  .add-project-btn:active { transform: translateY(1px); }

/* 设置按钮 */
.settings-button {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  padding: 8px;
  border-radius: 50%;
  transition: background-color 0.3s;
  margin-left: 20px;
  flex-shrink: 0;
}

.settings-button:hover {
  background-color: #f0f0f0;
}

/* 设置面板 */
.settings-panel {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4));
  backdrop-filter: blur(8px);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  animation: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes slideUp {
  from {
    transform: translateY(30px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.settings-content {
  background: linear-gradient(145deg, #ffffff, #f8f9fa);
  padding: 40px;
  border-radius: 20px;
  box-shadow: 
    0 20px 60px rgba(0, 0, 0, 0.15),
    0 8px 25px rgba(0, 0, 0, 0.1),
    inset 0 1px 0 rgba(255, 255, 255, 0.8);
  min-width: 650px;
  max-width: 800px;
  position: relative;
  animation: slideUp 0.4s ease-out;
  border: 1px solid rgba(255, 255, 255, 0.2);
}

.settings-content::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: linear-gradient(90deg, #2765a7, #4a90e2, #2765a7);
  border-radius: 20px 20px 0 0;
}

.settings-content h3 {
  margin: 0 0 30px 0;
  font-size: 24px;
  font-weight: 600;
  color: #2c3e50;
  text-align: center;
  position: relative;
  padding-bottom: 15px;
}

.settings-content h3::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 60px;
  height: 3px;
  background: linear-gradient(90deg, #2765a7, #4a90e2);
  border-radius: 2px;
}

.file-row {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-bottom: 30px;
  padding: 25px;
  border: 2px dashed #e1e8ed;
  border-radius: 15px;
  background: linear-gradient(135deg, #f8f9fa, #ffffff);
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;
}

.file-row::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(71, 144, 226, 0.1), transparent);
  transition: left 0.6s ease;
}

.file-row:hover {
  border-color: #4a90e2;
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(74, 144, 226, 0.15);
}

.file-row:hover::before {
  left: 100%;
}

.file-controls {
  display: flex;
  align-items: center;
  gap: 15px;
}

.file-button {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  background: linear-gradient(135deg, #4a90e2, #2765a7);
  color: white;
  border-radius: 10px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  white-space: nowrap;
  box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
  border: none;
  position: relative;
  overflow: hidden;
}

.file-button::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s ease;
}

.file-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(74, 144, 226, 0.4);
}

.file-button:hover::before {
  left: 100%;
}

.file-button:active {
  transform: translateY(0);
}

.file-icon {
  font-size: 18px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
}

.file-path {
  flex: 1;
  padding: 12px 16px;
  background: linear-gradient(135deg, #ffffff, #f8f9fa);
  border: 2px solid #e1e8ed;
  border-radius: 10px;
  color: #495057;
  min-height: 20px;
  display: flex;
  align-items: center;
  font-size: 14px;
  transition: all 0.3s ease;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
}

.file-path:hover {
  border-color: #4a90e2;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05), 0 0 0 3px rgba(74, 144, 226, 0.1);
}

.upload-button {
  padding: 12px 24px;
  background: linear-gradient(135deg, #28a745, #20c997);
  color: white;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  white-space: nowrap;
  box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
  position: relative;
  overflow: hidden;
}

.upload-button::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s ease;
}

.upload-button:hover:not(:disabled) {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
}

.upload-button:hover:not(:disabled)::before {
  left: 100%;
}

.upload-button:disabled {
  background: linear-gradient(135deg, #6c757d, #5a6268);
  cursor: not-allowed;
  transform: none;
  box-shadow: 0 2px 8px rgba(108, 117, 125, 0.2);
}

.file-input {
  display: none;
}

.upload-status {
  padding: 15px 20px;
  margin-bottom: 20px;
  border-radius: 10px;
  font-weight: 500;
  text-align: center;
  font-size: 14px;
  background: linear-gradient(135deg, #e3f2fd, #f3e5f5);
  border: 1px solid #e1e8ed;
  color: #2c3e50;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.button-group {
  display: flex;
  justify-content: flex-end;
  gap: 15px;
  margin-top: 20px;
}

.close-button {
  padding: 12px 24px;
  background: linear-gradient(135deg, #6c757d, #5a6268);
  color: white;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(108, 117, 125, 0.3);
  position: relative;
  overflow: hidden;
}

.close-button::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s ease;
}

.close-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(108, 117, 125, 0.4);
}

.close-button:hover::before {
  left: 100%;
}

/* 工序步骤样式 */
.operations-container {
  display: flex;
  flex-direction: column;
  height: 630px;
}

.operations-page {
  flex: 1;
  overflow-y: auto;
  padding-bottom: 10px;
}

.operation-item {
  margin-bottom: 15px;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  background-color: #f8f9fa;
}

.operation-header {
  padding: 12px 15px;
  background-color: #6b8caf;
  color: white;
  border-radius: 6px 6px 0 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.operation-number {
  background-color: rgba(255, 255, 255, 0.2);
  padding: 4px 8px;
  border-radius: 4px;
  font-weight: bold;
  min-width: 30px;
  text-align: center;
}

.operation-title {
  flex: 1;
  font-weight: 500;
}

.sub-operations {
  padding: 10px 15px;
}

.sub-operation {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid #e9ecef;
}

.sub-operation:last-child {
  border-bottom: none;
}

.sub-operation-number {
  background-color: #6c757d;
  color: white;
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 12px;
  font-weight: bold;
  min-width: 25px;
  text-align: center;
}

.sub-operation-title {
  flex: 1;
  color: #495057;
  font-size: 14px;
}

/* 分页控制样式 */
.pagination-controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 20px;
  background: linear-gradient(135deg, #f8f9fa, #ffffff);
  border-top: 1px solid #e9ecef;
  border-radius: 0 0 6px 6px;
  margin-top: auto;
}

.pagination-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 20px;
  background: linear-gradient(135deg, #6b8caf, #5a7ba0);
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(107, 140, 175, 0.3);
  min-width: 100px;
  justify-content: center;
}

.pagination-btn:hover:not(:disabled) {
  background: linear-gradient(135deg, #5a7ba0, #4a6b90);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(107, 140, 175, 0.4);
}

.pagination-btn:disabled {
  background: linear-gradient(135deg, #6c757d, #5a6268);
  cursor: not-allowed;
  opacity: 0.6;
  transform: none;
  box-shadow: 0 1px 4px rgba(108, 117, 125, 0.2);
}

.btn-icon {
  font-size: 12px;
  font-weight: bold;
}

.page-indicator {
  display: flex;
  align-items: center;
  gap: 4px;
  background: white;
  padding: 8px 16px;
  border-radius: 20px;
  border: 2px solid #6b8caf;
  font-weight: 600;
  color: #6b8caf;
  box-shadow: 0 2px 8px rgba(107, 140, 175, 0.15);
}

.current-page {
  font-size: 16px;
  color: #2c3e50;
}

.page-separator {
  color: #6c757d;
  margin: 0 2px;
}

.total-pages {
  font-size: 14px;
  color: #6c757d;
}

/* 上传提示样式 */
.upload-prompt {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 400px;
  padding: 40px 20px;
}

.upload-prompt-content {
  text-align: center;
  max-width: 300px;
}

.upload-icon {
  font-size: 64px;
  margin-bottom: 20px;
  opacity: 0.7;
}

.upload-prompt-content h4 {
  margin: 0 0 15px 0;
  font-size: 20px;
  font-weight: 600;
  color: #2c3e50;
}

.upload-prompt-content p {
  margin: 0 0 25px 0;
  color: #6c757d;
  font-size: 14px;
  line-height: 1.5;
}

.upload-prompt-button {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px;
  background: linear-gradient(135deg, #4a90e2, #2765a7);
  color: white;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(74, 144, 226, 0.3);
  position: relative;
  overflow: hidden;
}

.upload-prompt-button::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  transition: left 0.5s ease;
}

.upload-prompt-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(74, 144, 226, 0.4);
}

.upload-prompt-button:hover::before {
  left: 100%;
}

.button-icon {
  font-size: 16px;
}

/* 跳转功能样式 */
.jump-controls {
  margin-top: 20px;
  padding: 15px;
  background: #f8f9fa;
  border-radius: 8px;
  border: 1px solid #e9ecef;
}

.jump-input-group {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.jump-label {
  font-size: 14px;
  font-weight: 500;
  color: #495057;
  white-space: nowrap;
}

.jump-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 14px;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.jump-input:focus {
  outline: none;
  border-color: #4a90e2;
  box-shadow: 0 0 0 0.2rem rgba(74, 144, 226, 0.25);
}

.jump-btn {
  padding: 8px 16px;
  background: #4a90e2;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: background-color 0.15s ease-in-out;
  white-space: nowrap;
}

.jump-btn:hover {
  background: #357abd;
}

.jump-btn:active {
  background: #2765a7;
}

.jump-hint {
  font-size: 12px;
  color: #6c757d;
  line-height: 1.4;
  margin-top: 5px;
}

.upload-prompt-button:hover::before {
  left: 100%;
}

.upload-prompt-button:active {
  transform: translateY(0);
}

.button-icon {
  font-size: 16px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
}

.placeholder-content {
  padding: 20px;
  text-align: center;
  color: #6c757d;
}

.placeholder-content p {
  margin: 0;
  font-style: italic;
}

/* 项目列表弹窗样式 */
.project-list-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.project-list-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(2px);
}

.project-list-content {
  position: relative;
  width: 95%;
  height: calc(100vh - 140px);
  max-width: 1400px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  margin-top: 70px;
}

.project-list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 30px;
  background-color: #f8f9fa;
  color: #495057;
  border-radius: 12px 12px 0 0;
  border-bottom: 1px solid #e9ecef;
}

.project-list-header h3 {
  margin: 0;
  font-size: 24px;
  font-weight: 600;
}

.close-modal-btn {
  background: rgba(255, 255, 255, 0.2);
  color: white;
  border: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 18px;
  font-weight: bold;
  transition: all 0.3s ease;
  display: flex;
  align-items: center;
  justify-content: center;
}

.close-modal-btn:hover {
  background: rgba(255, 255, 255, 0.3);
  transform: scale(1.1);
}

.project-list-table-container {
  flex: 1;
  overflow: auto;
  padding: 0;
  background: #f8f9fa;
}

.project-list-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
  font-size: 14px;
}

.project-list-table th {
  background: linear-gradient(135deg, #e9ecef, #f8f9fa);
  color: #495057;
  font-weight: 600;
  padding: 15px 12px;
  text-align: center;
  border: 1px solid #dee2e6;
  position: sticky;
  top: 0;
  z-index: 10;
  white-space: nowrap;
}

.project-list-table td {
  padding: 12px;
  text-align: center;
  border: 1px solid #dee2e6;
  vertical-align: middle;
  white-space: nowrap;
}

.project-row:nth-child(even) {
  background-color: #f8f9fa;
}

.project-row:hover {
  background-color: #e3f2fd;
}

/* 列宽控制 */
.col-序号 {
  width: 60px;
  min-width: 60px;
  text-align: center;
}

.col-项目编号 {
  width: 120px;
  min-width: 120px;
}

.col-项目名称 {
  width: 100px;
  min-width: 100px;
}

.col-产品名称 {
  width: 120px;
  min-width: 120px;
}

.col-耐压,
.col-开门时间,
.col-关门时间,
.col-塞拉力,
.col-推力,
.col-拉力,
.col-软件版本 {
  width: 100px;
  min-width: 100px;
}

/* 测试项单元格样式 */
.test-cell {
  cursor: pointer;
  transition: all 0.2s ease;
  padding: 8px !important;
}

.test-cell:hover {
  background-color: #e3f2fd !important;
  transform: scale(1.05);
}

.test-checkbox {
  width: 24px;
  height: 24px;
  border: 2px solid #6c757d;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto;
  transition: all 0.3s ease;
  background: white;
}

.test-checkbox.checked {
  background: linear-gradient(135deg, #28a745, #20c997);
  border-color: #28a745;
  color: white;
}

.checkmark {
  font-size: 16px;
  font-weight: bold;
  line-height: 1;
}

.project-list-footer {
  padding: 20px 30px;
  background: linear-gradient(135deg, #f8f9fa, #ffffff);
  border-top: 1px solid #dee2e6;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
  border-radius: 0 0 12px 12px;
}

.save-config-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px;
  background-color: #0d47a1;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  font-size: 16px;
  transition: all 0.3s ease;
  box-shadow: 0 4px 15px rgba(13, 71, 161, 0.2);
}

.save-config-btn:hover {
  background-color: #1976d2;
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(13, 71, 161, 0.3);
}

.btn-icon {
  font-size: 14px;
}

/* 响应式设计 */
@media (max-width: 1200px) {
  .project-list-content {
    width: 98%;
    height: calc(100vh - 100px);
    margin-top: 50px;
  }
  
  .project-list-table {
    font-size: 13px;
  }
  
  .project-list-table th,
  .project-list-table td {
    padding: 10px 8px;
  }
}

@media (max-width: 768px) {
  .project-list-content {
    width: 100%;
    height: 100vh;
    margin-top: 0;
    border-radius: 0;
  }
  
  .project-list-header {
    padding: 15px 20px;
    border-radius: 0;
  }
  
  .project-list-header h3 {
    font-size: 20px;
  }
  
  .project-list-table {
    font-size: 12px;
  }
  
  .project-list-table th,
  .project-list-table td {
    padding: 8px 6px;
  }
  
  .col-序号,
  .col-项目编号,
  .col-项目名称,
  .col-产品名称,
  .col-耐压,
  .col-开门时间,
  .col-关门时间,
  .col-塞拉力,
  .col-推力,
  .col-拉力,
  .col-软件版本 {
    width: auto;
    min-width: 60px;
  }
  
  .test-checkbox {
    width: 20px;
    height: 20px;
  }
  
  .checkmark {
    font-size: 14px;
  }
}

/* 项目列表弹窗头部操作按钮样式 */
.header-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.add-project-btn {
  background-color: #0d47a1;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(13, 71, 161, 0.2);
}

.add-project-btn:hover {
  background-color: #1976d2;
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(13, 71, 161, 0.3);
}

.add-project-btn:active {
  transform: translateY(0);
}

/* 删除按钮样式 */
.delete-project-btn {
  background: linear-gradient(135deg, #f44336, #d32f2f);
  color: white;
  border: none;
  padding: 6px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(244, 67, 54, 0.3);
}

.delete-project-btn .btn-icon {
  color: white;
  font-weight: bold;
  font-size: 14px;
}

.delete-project-btn:hover {
  background: linear-gradient(135deg, #d32f2f, #b71c1c);
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(244, 67, 54, 0.4);
}

.delete-project-btn:active {
  transform: translateY(0);
}

/* 操作列样式 */
.col-操作 {
  width: 80px;
  text-align: center;
}

/* 添加项目表单弹窗样式 */
.add-project-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1001;
  display: flex;
  align-items: center;
  justify-content: center;
}

.add-project-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
}

.add-project-content {
  position: relative;
  background: white;
  border-radius: 12px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
  width: 90%;
  max-width: 600px;
  max-height: 80vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.add-project-header {
  background-color: #f8f9fa;
  color: #495057;
  padding: 16px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #e9ecef;
}

.add-project-header h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.add-project-form {
  padding: 24px;
  overflow-y: auto;
  flex: 1;
}

.form-row {
  display: flex;
  gap: 16px;
  margin-bottom: 20px;
}

.form-group {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.form-group label {
  font-weight: 500;
  margin-bottom: 6px;
  color: #333;
  font-size: 14px;
}

.form-input {
  padding: 10px 12px;
  border: 2px solid #e0e0e0;
  border-radius: 6px;
  font-size: 14px;
  transition: all 0.3s ease;
  background: #fafafa;
}

.form-input:focus {
  outline: none;
  border-color: #2196F3;
  background: white;
  box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);
}

.form-section {
  margin-top: 24px;
}

.form-section h4 {
  margin: 0 0 16px 0;
  color: #333;
  font-size: 16px;
  font-weight: 600;
}

.test-items-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.test-item-checkbox {
  display: flex;
  align-items: center;
}

.checkbox-label {
  display: flex;
  align-items: center;
  cursor: pointer;
  font-size: 14px;
  color: #555;
  transition: color 0.3s ease;
}

.checkbox-label:hover {
  color: #2196F3;
}

.checkbox-input {
  margin-right: 8px;
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.checkbox-text {
  user-select: none;
}

.add-project-footer {
  background: #f8f9fa;
  padding: 16px 24px;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  border-top: 1px solid #e0e0e0;
}

.cancel-btn {
  background: #6c757d;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.3s ease;
}

.cancel-btn:hover {
  background: #5a6268;
  transform: translateY(-1px);
}

.confirm-btn {
  background-color: #0d47a1;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(13, 71, 161, 0.2);
}

.confirm-btn:hover {
  background-color: #1976d2;
  transform: translateY(-1px);
  box-shadow: 0 4px 8px rgba(13, 71, 161, 0.3);
}

.confirm-btn:active,
.cancel-btn:active {
  transform: translateY(0);
}

/* 响应式设计 */
 @media (max-width: 768px) {
   .add-project-content {
     width: 95%;
     max-height: 90vh;
   }
   
   .form-row {
     flex-direction: column;
     gap: 12px;
   }
   
   .test-items-grid {
     grid-template-columns: 1fr;
   }
   
   .add-project-footer {
     flex-direction: column;
   }
   
   .header-actions {
     flex-direction: column;
     gap: 8px;
   }
 }
</style>
    const wsErrorMessage = ref('')
    const wsErrorProjectCode = ref('')